<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="shortcut icon" href="">
    <title>弹性</title>
    <style type="text/css">
        body {
            background-color: #eccc68;
        }

        canvas {
            width: 400px;
            height: 300px;
            border: 1px solid black;
        }
    </style>
</head>

<body>
    <h1>弹性</h1>
    <script type="text/javascript">
        const WIDTH = 800, HEIGHT = 600
        const gravity = 680
        const edgeWidth = 50
        const totalBalls = 40
        // 弹性系数
        const elastic = 5
        let canvas = document.createElement('canvas')
        canvas.width = WIDTH
        canvas.height = HEIGHT
        canvas.style.width = (canvas.width / 2) + "px"
        canvas.style.height = (canvas.height / 2) + "px"
        canvas.style.border = "1px solid black"
        let ctx = canvas.getContext('2d')
        document.querySelector('body').appendChild(canvas)
        let info = document.createElement('canvas')
        info.width = WIDTH / 2
        info.height = HEIGHT
        info.style.width = (info.width / 2) + "px"
        info.style.height = (info.height / 2) + "px"
        info.style.border = "1px solid black"
        let info_ctx = info.getContext('2d')
        document.querySelector('body').appendChild(info)
        function randomv(min = 10, max = 20) {
            let range = min - ~~(Math.random() * max)
            return range > 0 ? range + min : range - min
        }
        class Gameboard {
            constructor(cor = 1) {
                this.startTime
                this.tickLength = 20
                this.lastTick = 0
                this.cor = cor
                this.init()
            }
            init() {
                // ctx.circleId = 0
                let radius = 20, radians2 = 20
                this.circles = [
                    new Circle(ctx, 450, 10, radius, randomv(), randomv(), radius, 1),
                    new Circle(ctx, 450, 300, radians2, randomv(), randomv(), radians2, 2),
                    new Circle(ctx, 60, 240, radians2, randomv(), randomv(), radians2, 3),
                    /*
                    new Circle(ctx, 120, 210, radians2, randomv(), randomv(), radians2, 4),
                    new Circle(ctx, 20, 70, radians2, randomv(), randomv(), radians2, 4),
                    new Circle(ctx, 320, 290, radians2, randomv(), randomv(), radians2, 5),
                    */
                ]
                this.rects = [
                    /*
                    new Rect(ctx, 150, 150, 150, 5, 0),
                    new Rect(ctx, 50, 250, 120, 5, 0),
                    new Rect(ctx, 150, 350, 130, 5, 0),
                    new Rect(ctx, 250, 450, 250, 5, 0),
                        new Rect(ctx, 50, 550, 140, 5, 0),
                        new Rect(ctx, 550, 500, 190, 5, 0),
                        new Rect(ctx, 350, 200, 150, 5, 0),
                        new Rect(ctx, 250, 300, 250, 5, 0),
                        new Rect(ctx, 150, 400, 450, 5, 0),
                        new Rect(ctx, 450, 100, 250, 5, 0),
                        new Rect(ctx, 250, 100, 150, 5, 0),
                        new Rect(ctx, 50, 50, 250, 5, 0),
                    */
                ]
                this.lines = [
                    /*
                    new Line(ctx, 120, 120, 320, 320),
                    new Line(ctx, 320, 120, 520, 320),
                    new Line(ctx, 220, 320, 420, 520),
                    new Line(ctx, 420, 320, 620, 520),
                    new Line(ctx, 160, 220, 320, 120),
                    new Line(ctx, 320, 120, 550, 180),
                    new Line(ctx, 320, 220, 550, 280),
                    new Line(ctx, 20, 120, 150, 280),
                    */
                ]
                this.info = new Info(info_ctx)
                // requestAnimationFrame(this.process.bind(this));
                // console.log(this.circles)
                this.process(performance.now())
            }
            process(tFrame) {
                this.stopKey = requestAnimationFrame(this.process.bind(this))
                let nextTick = this.lastTick + this.tickLength
                let tickNum = 0
                if (tFrame > nextTick) {
                    tickNum = Math.floor((tFrame - this.lastTick) / this.tickLength)
                }
                if (tickNum > 1000) {
                    this.lastTick = tFrame
                    return
                }
                this.update(tickNum)
                this.render()
                this.startTime = tFrame
                // requestAnimationFrame(this.process.bind(this))
            }
            update(tickNum) {
                for (let j = 0; j < tickNum; ++j) {
                    this.lastTick += this.tickLength
                    for (let i = 0; i < this.circles.length; i++) {
                        this.circles[i].fps = this.tickLength / 1000
                    }
                    this.checkCollision()
                    for (let i = 0; i < this.circles.length; i++) {
                        this.circles[i].update()
                    }
                    let groups = []
                    this.rects.forEach(item => {
                        if (groups[item.group] === undefined) {
                            groups[item.group] = 1
                        } else {
                            groups[item.group] += 1
                        }
                    })
                    this.info.update(groups)
                }
            }
            render() {
                ctx.clearRect(0, 0, WIDTH, HEIGHT)
                for (let i = 0; i < this.circles.length; i++) {
                    // console.log(this.circles[i].position)
                    this.circles[i].draw(ctx)
                }
                for (let i = 0; i < this.rects.length; ++i) {
                    this.rects[i].draw()
                }
                for (let i = 0; i < this.lines.length; ++i) {
                    this.lines[i].draw()
                }
                this.info.draw()
            }

            checkCollision() {
                this.circles.forEach(circle => {
                    circle.colliding = false
                    let cor = this.cor
                    if (circle.position.x - circle.r <= 0) {
                        circle.velocity.x *= cor * -1
                        circle.position.x = circle.r + 1
                        // circle.colliding = true
                    }
                    if (circle.position.x + circle.r >= WIDTH) {
                        circle.velocity.x *= cor * -1
                        circle.position.x = WIDTH - circle.r - 1
                        // circle.colliding = true
                    }
                    if (circle.position.y - circle.r <= 0) {
                        circle.velocity.y *= cor * -1
                        circle.position.y = circle.r + 1
                        // circle.colliding = true
                    }
                    if (circle.position.y + circle.r > HEIGHT) {
                        circle.velocity.y *= (1) * -1
                        circle.position.y = HEIGHT - circle.r
                        // circle.colliding = true
                    }
                    circle.acce_speed = new Vector(0, 0)
                    for (let i = 0, l = this.circles.length; i < l; ++i) {
                        let cur = this.circles[i]
                        if (circle.id == cur.id) continue
                        circle.acce_speed = circle.acce_speed.add(this.keepdistance(circle, cur))
                        // continue
                        if (circle.isCircleCollided(cur)) {
                            circle.colliding = true
                            cur.colliding = true
                            circle.changeVelocityAndDirection(cur, cor)
                        } else {
                            // this.gravitation(circle, cur)
                        }
                    }
                    // console.log(circle.acce_speed)
                    for (let i = 0, l = this.rects.length; i < l; ++i) {
                        let rect = this.rects[i]
                        if (circle.group == rect.group) continue
                        if (this.isCircleCollidedRect(circle, rect)) {
                            circle.colliding = true
                            rect.group = circle.group
                        }
                    }
                    for (let i = 0, l = this.lines.length; i < l; ++i) {
                        let line = this.lines[i]
                        if (circle.group == line.group) continue
                        if (this.isCircleCollidedLine(circle, line)) {
                            circle.colliding = true
                            // rect.group = circle.group
                        }
                        line.rotate(1 / 60, new Vector((line.x1 + line.x2) / 2, (line.y1 + line.y2) / 2))
                    }
                })
            }

            keepdistance(c1, c2) {
                let vec = c1.position.substract(c2.position)
                let vec_normal = vec.normalize()
                let distance = vec.normal() - 5 * (c1.r + c2.r)
                // f = v * m * k / (m - k)
                let f = Math.abs(c1.velocity.dot(vec_normal)) * c1.mass * elastic / (c1.mass - elastic)
                // let f = 50
                if (distance > 0) {
                    return vec_normal.multiply(f * -1).divide(c1.mass)
                    // c1.velocity = c1.velocity.substract(vec_normal.multiply(f).divide(c1.mass))
                    // c2.velocity = c2.velocity.add(vec_normal.multiply(f).divide(c2.mass))
                } else if (distance < 0) {
                    return vec_normal.multiply(f).divide(c1.mass)
                    // c1.velocity = c1.velocity.add(vec_normal.multiply(f).divide(c1.mass))
                    // c2.velocity = c2.velocity.substract(vec_normal.multiply(f).divide(c2.mass))
                }
                return new Vector(0, 0)
            }

            gravitation(c1, c2) {
                let vec = c1.position.substract(c2.position)
                let fval = 500 * c1.mass * c2.mass / Math.pow(vec.normal(), 2)
                let f = vec.normalize().multiply(fval)
                c1.velocity = c1.velocity.substract(f.divide(c1.mass))
                /*
                if (c1.id === 2) {
                    console.log(c1.id, f.divide(c1.mass))
                    console.log(c1.id, c1.velocity)
                }
                */
            }
            isCircleCollidedRect(circle, rect) {
                let cor = this.cor
                // 左
                if (circle.position.x < rect.x && circle.position.x + circle.r >= rect.x && circle.position.y >= rect.y && circle.position.y <= rect.y + rect.h) {
                    circle.velocity.x *= cor * -1
                    circle.position.x = rect.x - circle.position.r
                    return true
                }
                if (circle.position.x + circle.r >= rect.x) {
                    // 左上
                    if (circle.position.y + circle.r >= rect.y) {
                        let distance = Math.sqrt(Math.pow(circle.position.x - rect.x, 2) + Math.pow(circle.position.y - rect.y, 2))
                        if (distance < circle.r) {
                            let p = this.getedgePoint(circle.position.x, circle.position.y, circle.r, rect.x, rect.y, circle.velocity.normalize())
                            circle.position.x = p.x
                            circle.position.y = p.y
                            // 处理速度
                            let vafter = this.reflact(new Vector(rect.x - circle.position.x, rect.y - circle.position.y), circle.velocity, cor)
                            circle.velocity = vafter
                            return true
                        }
                    }
                    // 左下
                    if (circle.position.y - circle.r <= rect.y + rect.h) {
                        let distance = Math.sqrt(Math.pow(circle.position.x - rect.x, 2) + Math.pow(circle.position.y - rect.y - rect.h, 2))
                        if (distance < circle.r) {
                            let p = this.getedgePoint(circle.position.x, circle.position.y, circle.r, rect.x, rect.y + rect.h, circle.velocity.normalize())
                            circle.position = p
                            // 处理速度
                            let vafter = this.reflact(new Vector(rect.x - circle.position.x, rect.y - circle.position.y), circle.velocity, cor)
                            circle.velocity = vafter
                            return true
                        }
                    }
                }
                // 右
                if (circle.position.x > rect.x + rect.w && circle.position.x - circle.r <= rect.x + rect.w && circle.position.y >= rect.y && circle.position.y <= rect.y + rect.h) {
                    circle.velocity.x *= cor * -1
                    circle.position.x = rect.x + rect.w + circle.r
                    return true
                }
                if (circle.position.x - circle.r <= rect.x + rect.w) {
                    // 右上
                    if (circle.position.y + circle.r >= rect.y) {
                        let distance = Math.sqrt(Math.pow(circle.position.x - rect.x - rect.w, 2) + Math.pow(circle.position.y - rect.y, 2))
                        if (distance < circle.r) {
                            let p = this.getedgePoint(circle.position.x, circle.position.y, circle.r, rect.x + rect.w, rect.y, circle.velocity.normalize())
                            circle.position = p
                            // 处理速度
                            let vafter = this.reflact(new Vector(rect.x + rect.w - circle.position.x, rect.y - circle.position.y), circle.velocity, cor)
                            circle.velocity = vafter
                            return true
                        }
                    }
                    // 右下
                    if (circle.position.y - circle.r <= rect.y + rect.h) {
                        let distance = Math.sqrt(Math.pow(circle.position.x - rect.x - rect.w, 2) + Math.pow(circle.position.y - rect.y - rect.h, 2))
                        if (distance < circle.r) {
                            let p = this.getedgePoint(circle.position.x, circle.position.y, circle.r, rect.x + rect.w, rect.y + rect.h, circle.velocity.normalize())
                            circle.position = p
                            // 处理速度
                            let vafter = this.reflact(new Vector(rect.x + rect.w - circle.position.x, rect.y + rect.h - circle.position.y), circle.velocity, cor)
                            circle.velocity = vafter
                            return true
                        }
                    }
                }
                // 上
                if (circle.position.y < rect.y && circle.position.y + circle.r >= rect.y && circle.position.x >= rect.x && circle.position.x <= rect.x + rect.w) {
                    circle.velocity.y *= cor * -1
                    circle.position.y = rect.y - circle.r - 1
                    return true
                }
                // 下
                if (circle.position.y > rect.y + rect.h && circle.position.y - circle.r <= rect.y + rect.h && circle.position.x >= rect.x && circle.position.x <= rect.x + rect.w) {
                    circle.velocity.y *= cor * -1
                    circle.position.y = rect.y + rect.h + circle.r + 1
                    return true
                }
            }


            isCircleCollidedLine(circle, line) {
                let cor = this.cor
                let line_vec = new Vector(line.x2 - line.x1, line.y2 - line.y1)
                let p12Circle = new Vector(circle.position.x - line.x1, circle.position.y - line.y1)
                let p22Circle = new Vector(circle.position.x - line.x2, circle.position.y - line.y2)
                let cos = line_vec.dot(p12Circle) / (line_vec.normal() * p12Circle.normal())
                // 投影长度
                let lp1pr_normal = p12Circle.normal() * cos

                // 法线
                let nor_vec = p12Circle.substract(line_vec.normalize().multiply(lp1pr_normal)).normalize()
                // let nor_vec = new Vector(-line_vec.normalize().y, line_vec.normalize().x)


                let velocity = new Vector(circle.vx, circle.vy)

                // 端点
                if (cos < 0 && p12Circle.normal() <= circle.r) {
                    let positions = new Vector(line.x1, line.y1).add(p12Circle.normalize().multiply(circle.r))
                    circle.position = positions
                    let vafter = this.reflact(p12Circle, circle.velocity, cor)
                    circle.velocity = vafter

                    return true
                }
                if (lp1pr_normal > line_vec.normal() && p22Circle.normal() <= circle.r) {
                    let positions = new Vector(line.x2, line.y2).add(p22Circle.normalize().multiply(circle.r))
                    circle.position = positions
                    let vafter = this.reflact(p22Circle, circle.velocity, cor)
                    circle.velocity = vafter
                    return true
                }
                // 段中点
                // console.log(p12Circle.dot(line_vec) / (p12Circle.normal() * cos))
                if (cos >= 0 && lp1pr_normal <= line_vec.normal()) {
                    // let pr = new Vector(line.x1, line.y1).add(line_vec.normalize().multiply(lp1pr_normal))
                    let pr = line_vec.normalize().multiply(lp1pr_normal)
                    if (p12Circle.substract(pr).normal() <= circle.r) {
                        let positions = new Vector(line.x1, line.y1).add(line_vec.normalize().multiply(lp1pr_normal)).add(nor_vec.multiply(circle.r))
                        circle.position = positions
                        let vafter = this.reflact(nor_vec, circle.velocity, cor)
                        circle.velocity = vafter
                    }
                    return true
                }
                return false
            }
            /**
              * @param normal 法线
              * @param velocity 速度
              * @param cor 衰减系数
              */
            reflact(normal, velocity, cor) {
                // 法线
                let unitVNorm = normal.normalize()
                // 切线
                let unitVTan = new Vector(-unitVNorm.y, unitVNorm.x)
                // let velocity = new Vector(circle.vx, circle.vy)
                // 法线方向的投影(标量)
                let vn = velocity.dot(unitVNorm)
                // 切线方向的投影(标量)
                let vt = velocity.dot(unitVTan)
                // 法线方向取反
                let vNorm = unitVNorm.multiply(vn * -1)
                // 切线方向不变
                let vTan = unitVTan.multiply(vt)

                let vafter = vNorm.add(vTan)
                return vafter
            }

            getedgePoint(x, y, r, rx, ry, dir) {
                // 当前位置
                let p = new Vector(x, y)
                // 矩形点,圆心
                let c = new Vector(rx, ry)
                // 中心点
                let pr = p.add(dir.multiply(c.substract(p).dot(dir)).divide(dir.x * dir.x + dir.y * dir.y))
                // 中点指向圆心
                let prd = pr.substract(c)
                // 单边长度
                let side = Math.sqrt(Math.pow(r, 2) - Math.pow(prd.x, 2) - Math.pow(prd.y, 2))

                let p1 = pr.add(dir.multiply(side))
                if (p1.substract(p).normal() < side) {
                    return p1
                }
                let p2 = pr.substract(dir.multiply(side))
                if (p2.substract(p).normal() < side) {
                    return p2
                }
            }
        }
        class Info {
            constructor(context, groups = []) {
                this.context = context
                this.groups = groups
                this.fps = 0
            }
            update(groups) {
                let group_ = []
                groups.forEach((val, index) => {
                    if (index > 0) {
                        group_.push({
                            key: index,
                            val: val,
                            color: "hsl(" + (index * 80) + ", 100%, 50%)"
                        })
                    }
                })
                this.groups = group_.sort((a, b) => {
                    return a.val - b.val > 0 ? 1 : -1
                }).reverse()
            }
            draw() {
                this.context.clearRect(0, 0, WIDTH / 2, HEIGHT)
                for (let i = 0, l = this.groups.length; i < l; ++i) {
                    let step = 3
                    this.context.fillStyle = this.groups[i].color
                    this.context.beginPath()
                    this.context.fillRect(50, 50 + i * 40, this.groups[i].val * step, 40)
                    this.context.font = "30px serif"
                    this.context.fillText(this.groups[i].val + "", 60 + this.groups[i].val * step, 50 + i * 40 + 30)
                    this.context.fill()
                    this.context.stroke()

                }
            }
        }

        class Line {
            constructor(context, x1, y1, x2, y2, width = 5, group = 0) {
                if (!context.lineId) context.lineId = 1
                context.lineId += 1
                this.id = context.lineId
                this.context = context
                this.x1 = x1
                this.y1 = y1
                this.x2 = x2
                this.y2 = y2
                this.width = width
                this.fps = 0
                this.group = group
            }
            draw() {
                let strokeStyle = "hsla(" + (this.group * 80) + ", 100%, 50%, .9)"
                this.context.strokeStyle = strokeStyle
                this.context.beginPath()
                this.context.moveTo(this.x1, this.y1)
                this.context.lineTo(this.x2, this.y2)
                this.context.lineWidth = this.width
                this.context.stroke()
                this.context.closePath()
            }
            rotate(angle, origin = undefined) {
                if (!origin || !(origin instanceof Vector)) {
                    let line_vec = new Vector(this.x2 - this.x1, this.y2 - this.y1)
                    let line = new Vector(this.x1, this.y1).add(line_vec.normalize().rotate(angle).multiply(line_vec.normal()))
                    this.x2 = line.x
                    this.y2 = line.y
                } else {
                    let line1 = new Vector(this.x1 - origin.x, this.y1 - origin.y)
                    let line = new Vector(origin.x, origin.y).add(line1.normalize().rotate(angle).multiply(line1.normal()))
                    this.x1 = line.x
                    this.y1 = line.y
                    let line2 = new Vector(this.x2 - origin.x, this.y2 - origin.y)
                    line = new Vector(origin.x, origin.y).add(line2.normalize().rotate(angle).multiply(line2.normal()))
                    this.x2 = line.x
                    this.y2 = line.y

                }
            }
        }
        class Rect {
            constructor(context, x, y, w, h, group = 0) {
                if (!context.circleId) context.circleId = 1
                context.circleId += 1
                this.id = context.circleId
                this.context = context
                this.x = x
                this.y = y
                this.w = w
                this.h = h
                this.fps = 0
                this.group = group
            }
            draw() {
                let fillStyle = "hsla(" + (this.group * 80) + ", 100%, 50%, .5)"
                this.context.fillStyle = fillStyle
                this.context.beginPath()
                this.context.fillRect(this.x, this.y, this.w, this.h)
                this.context.fill()
                this.context.closePath()
            }
        }
        class Circle {
            constructor(context, x, y, r, vx, vy, mass = 1, group = 0) {
                if (!context.circleId) context.circleId = 0
                context.circleId += 1
                this.id = context.circleId
                this.context = context
                this.x = x
                this.y = y
                this.position = new Vector(x, y)
                this.r = r
                this.velocity = new Vector(vx, vy)
                this.vx = vx
                this.vy = vy
                this.colliding = false
                this.mass = mass
                this.fps = 0
                this.group = group
                this.positions = [this.position]
                this.acce_speed = new Vector(0, 0)
            }
            // 绘制小球
            draw() {
                // this.context.fillStyle = "hsl(170, 100%, 50%)"
                let fillStyle = "hsla(" + (this.group * 80) + ", 100%, 50%, 1)"
                this.context.fillStyle = fillStyle
                // this.context.fillStyle = this.colliding ? "hsl(300, 100%, 70%)" : "hsl(170, 100%, 50%)"

                this.context.strokeStyle = "hsl(220, 100%, 50%)"
                this.context.strokeStyle = this.colliding ? "hsl(120, 100%, 50%)" : "hsl(220, 100%, 50%)"

                this.context.beginPath()
                this.context.arc(this.position.x, this.position.y, this.r, 0, 2 * Math.PI)
                this.context.fill()
                this.context.lineWidth = 1
                this.context.stroke()
                this.context.closePath()
                // this.positions.forEach((position, index) => {
                //     console.log(this.id, index, position)
                // })
                this.context.beginPath()
                this.context.moveTo(this.positions[0].x, this.positions[0].y)
                for (let i = 1, l = this.positions.length; i < l; ++i) {
                    this.context.lineTo(this.positions[i].x, this.positions[i].y);
                    this.context.lineWidth = this.r * 2 * (1 - i / this.positions.length);
                    this.context.strokeStyle = fillStyle
                    this.context.stroke();
                }
                this.context.closePath()

                /*
                */
            }
            /**
           * 更新画布
           * @param {number} seconds
           */
            update() {
                this.vy += gravity * this.fps
                // if (this.id == 1) return
                this.velocity = this.velocity.add(new Vector(0, gravity * this.fps))
                // console.log(this.acce_speed)
                this.velocity = this.velocity.add(this.acce_speed)
                this.position = this.position.add(this.velocity.multiply(this.fps))
                this.positions.unshift(this.position)
                if (this.positions.length > 5) {
                    this.positions.pop()
                }
                /*
                */
            }
            isCircleCollided(other) {
                let vec = this.position.substract(other.position)
                let distance = vec.normal()
                let radius = (this.r + other.r);

                if (distance === radius) {
                    return true
                } else if (distance < radius) {
                    // console.log('gap: ' + (radius - distance))
                    let gap = (radius - distance) / 2
                    let unit1t2Norm = vec.normalize()
                    let tmp1 = this.position.add(unit1t2Norm.multiply(gap))
                    if (tmp1.x <= this.r) {
                        tmp1.x = this.r
                    }
                    if (tmp1.x >= WIDTH - this.r) {
                        tmp1.x = WIDTH - this.r
                    }
                    if (tmp1.y <= this.r) {
                        tmp1.y = this.r
                    }
                    if (tmp1.y >= HEIGHT - this.r) {
                        tmp1.y = HEIGHT - this.r
                    }
                    // console.log(this.position, tmp1)
                    this.position = tmp1
                    this.velocity = this.velocity.add(unit1t2Norm.multiply(gap))

                    // let unit2t1Norm = new Vector(-unit1t2Norm.y, unit1t2Norm.x)
                    let unit2t1Norm = unit1t2Norm.multiply(-1)
                    tmp1 = other.position.add(unit2t1Norm.multiply(gap))
                    if (tmp1.x <= other.r) {
                        tmp1.x = other.r
                    }
                    if (tmp1.x >= WIDTH - other.r) {
                        tmp1.x = WIDTH - other.r
                    }
                    if (tmp1.y <= other.r) {
                        tmp1.y = other.r
                    }
                    if (tmp1.y >= HEIGHT - other.r) {
                        tmp1.y = HEIGHT - other.r
                    }
                    other.position = tmp1
                    other.velocity = other.velocity.add(unit2t1Norm.multiply(gap))
                    return true
                } else {
                    return false
                }

                return distance <= radius;
            }
            changeVelocityAndDirection(other, cor) {
                // 小球1圆心指向小球2圆心连线方向的向量
                let vNorm = this.position.substract(other.position)
                // 小球1圆心指向小球2圆心连线方向的单位向量
                let unitVNorm = vNorm.normalize()
                // 碰撞点处的切线方向单位向量
                let unitVTan = new Vector(-unitVNorm.y, unitVNorm.x)
                // 小球1速度在连线方向的投影(标量)
                let v1n = this.velocity.dot(unitVNorm)
                // 小球1速度在切线方向的投影(标量)
                let v1t = this.velocity.dot(unitVTan)
                // 小球2速度在连线方向的投影(标量)
                let v2n = other.velocity.dot(unitVNorm)
                // 小球2速度在切线方向的投影(标量)
                let v2t = other.velocity.dot(unitVTan)
                // 连线方向的投影代入公式计算得到碰撞之后的速度值(标量)
                // 完全弹性碰撞
                /*
                let v1nAfter = (v1n * (this.mass - other.mass) + 2 * other.mass * v2n) / (this.mass + other.mass)
                let v2nAfter = (v2n * (other.mass - this.mass) + 2 * this.mass * v1n) / (this.mass + other.mass)
                */
                // 衰减系数
                /*
                */
                let v1nAfter = (this.mass * v1n + other.mass * v2n + cor * other.mass * (v2n - v1n)) / (this.mass + other.mass)
                let v2nAfter = (this.mass * v1n + other.mass * v2n + cor * this.mass * (v1n - v2n)) / (this.mass + other.mass)
                if (v1nAfter < v2nAfter) return
                // 连线方向的速度值乘以连线方向的单位向量得到碰撞后的连线方向速度分量
                let v1VectorNorm = unitVNorm.multiply(v1nAfter)
                let v2VectorNorm = unitVNorm.multiply(v2nAfter)
                // 切线方法的速度值乘以切线方向的单位向量得到碰撞后切线方向速度分量
                let minVy = 10, minVx = 0
                // v1t = Math.abs(v1t) < minV ? 0 : v1t
                // v2t = Math.abs(v2t) < minV ? 0 : v2t
                let v1VectorTan = unitVTan.multiply(v1t)
                let v2VectorTan = unitVTan.multiply(v2t)
                let velocity1After = v1VectorNorm.add(v1VectorTan)
                let velocity2After = v2VectorNorm.add(v2VectorTan)
                // 设置碰撞后的速度
                this.velocity = velocity1After
                other.velocity = velocity2After
            }
        }

        class Vector {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }
            /**
             * 向量加法
             * @param {Vector} v
             */
            add(v) {
                return new Vector(this.x + v.x, this.y + v.y);
            }
            /**
             * 向量减法
             * @param {Vector} v
             */
            substract(v) {
                return new Vector(this.x - v.x, this.y - v.y);
            }
            /**
             * 向量与标量乘法
             * @param {Vector} s
             */
            multiply(s) {
                return new Vector(this.x * s, this.y * s);
            }
            /**
             * 向量与向量点乘（投影）
             * @param {Vector} v
             */
            dot(v) {
                return this.x * v.x + this.y * v.y;
            }
            /**
             * 向量与标量除法
             * @param {Vector} d
             */
            divide(d) {
                return new Vector(this.x / d, this.y / d);
            }
            /**
             * 向量标准化（除去长度）
             */
            normalize() {
                let distance = Math.sqrt(this.x * this.x + this.y * this.y);
                return new Vector(this.x / distance, this.y / distance);
            }
            /**
             * 向量模
             */
            normal() {
                return Math.sqrt(this.x * this.x + this.y * this.y)
            }
            /**
             * 旋转角度angle
             * @param {Vector} v
             */
            rotate(angle) {
                let radians = angle / (Math.PI * 2)
                return new Vector(this.x * Math.cos(radians) - this.y * Math.sin(radians), this.x * Math.sin(radians) + this.y * Math.cos(radians))
            }
        }
        let gBoard = new Gameboard()

    </script>
</body>

</html>